/**
 * DSM - Distributed Search Manager
 * Developed by Milspec Research International Pty Ltd
 * $Author: gsingh $
 * $Revision: 1.6 $
 * $Date: 2005/12/23 02:15:57 $
 * (c)Copyright 2004
 * education.au limited
 * DEST
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the names education.au limited, DEST nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 * To the extent permitted by law, the copyright owners of this software and
 * its contributors:
 * (i) exclude all warranties in relation to the software; and
 * (ii) exclude liability for all losses, costs, expenses and damages arising
 * in any way from the use of the software whether arising from or in
 * relation to breach of contract, negligence or any other tort, in equity
 * or otherwise. If the software is in breach of a warranty which is
 * implied by law, the copyright owners and contributors liability is
 * limited to the replacement of the software.
 *
 * @author	gsingh
 */

package au.edu.educationau.opensource.dsm.worker;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;

import org.apache.log4j.Logger;

import au.edu.educationau.opensource.dsm.adapters.SearchAdapter;
import au.edu.educationau.opensource.dsm.adapters.SearchAdapterProperties;
import au.edu.educationau.opensource.dsm.service.PropertiesService;
import au.edu.educationau.opensource.dsm.util.StringUtils;

/**
 * Provides pre-configured adapter objects to the Search manager. This allows
 * much faster population time and decreased load on system.
 * 
 * An adapter is a generic tool that can be extended by specifying different
 * values for its ADAPTER_PROPERTY_X values as available below. They are placed
 * in the properties file and loaded dynamically at startup.
 * 
 * An adapter instance or repository is the extended adapter as described above.
 * 
 */

public final class SearchAdapterFactory {
	static Logger logger = Logger.getLogger(SearchAdapterFactory.class.getName());

	/** Number of adapters to handle * */
	private static final int ADAPTER_COUNT = 25;
	/** Table where the adapters Class objects are held * */
	private static Hashtable adapters = new Hashtable(ADAPTER_COUNT); // increase this number to number of adapter types
	/** Table where the adapter's properties are held * */
	private static Hashtable properties = new Hashtable(ADAPTER_COUNT); // increase this number to number of adapter types
	/** Table where the adapter's category URLs are held * */
	private static Hashtable categoryURLS = new Hashtable(ADAPTER_COUNT); // increase this to the number of adapter types

	/** Array of sources */
	private static String[] searchableResources = null;

	/** All of the adapter instances, comma delimited */
	public static final String ADAPTER_INSTANCES = "adapter.instances";
	/**
	 * The class path of the generic adapter
	 * au.edu.educationau.opensource.dsm.adapters.SearchAdapter type.
	 */
	public static final String ADAPTER_PROPERTY_CLASSNAME = ".adapter.classname";
	/** The name of the repository that will be displayed in the result set */
	public static final String ADAPTER_PROPERTY_DISPLAY_TITLE = ".display.title";
	/** The description of the repository */
	public static final String ADAPTER_PROPERTY_DESCRIPTION = ".description";
	/** The human readable website URL for this repository */
	public static final String ADAPTER_PROPERTY_WEBSITE_URL = ".website.url";
	/**
	 * A logo or other insignia uniquely marking the repository. Used as part of
	 * result set as an icon.
	 */
	public static final String ADAPTER_PROPERTY_IMAGE_URL = ".image.url";
	/** The actual search functionality URL / URI */
	public static final String ADAPTER_PROPERTY_SEARCH_URL = ".search.url";
	/** The HTTP method to use when making requests to the search URL */
	public static final String ADAPTER_PROPERTY_METHOD = ".method";
	/**
	 * The default priority of this repository when Priority Interleaving is
	 * used as a SORT ALGORITHM
	 */
	public static final String ADAPTER_PROPERTY_PRIORITY = ".priority";
	/**
	 * The value to use as part of a ratio between the selected repositories of
	 * a search job which governs how many results are added to the Most
	 * Relevant Results tab
	 */
	public static final String ADAPTER_PROPERTY_RESULTMIX_FACTOR = ".result.mix.factor";
	/** The value to vary the relevance by using a multiplication operator */
	public static final String ADAPTER_PROPERTY_RELEVANCE_MULT = ".relevance.multiplier";
	/** The value to vary the relevance by using a addition operator */
	public static final String ADAPTER_PROPERTY_RELEVANCE_OFFSET = ".relevance.offset";
	/** The number of results to get get in one batch for this repository */
	public static final String ADAPTER_PROPERTY_BATCH_SIZE = ".batch.size";
	/** Flag to cause a rerank on results using the hit frequency calculator */
	public static final String ADAPTER_PROPERTY_RERANK = ".rerank";
	/** The URL/URI of where the categor(y/ies) for this result can be viewed */
	public static final String ADAPTER_PROPERTY_CATEGORY_URL = ".category.url";

	public static String[] sortedSources = new String[0];

	/** Destroy objects worthy of destruction */
	public static void destroy() {
		adapters.clear();
		properties.clear();
		categoryURLS.clear();
		logger.debug("SearchAdapterFactory shutdown ok.");
	}

	/**
	 * Initialise the adapter factory. Loads the properties file and assembles a
	 * cache of adapter Classes
	 */
	public static void init() {
		logger.info("Building Search Adapter Factory...");
		StringTokenizer adToken = new StringTokenizer(PropertiesService.getStringProperty(ADAPTER_INSTANCES, ""), ",");
		int count = 0;
		while (adToken.hasMoreTokens()) {
			String adapter = adToken.nextToken().toLowerCase();
			try {
				adapters.put(adapter, Class.forName(PropertiesService.getStringProperty(adapter + ADAPTER_PROPERTY_CLASSNAME, "au.edu.educationau.opensource.dsm.adapters.SearchAdapter")));
				SearchAdapterProperties props = new SearchAdapterProperties();
				props.setAdapterCode(adapter);
				props.setDisplayTitle(PropertiesService.getStringProperty(adapter + ADAPTER_PROPERTY_DISPLAY_TITLE, ""));
				props.setDescription(PropertiesService.getStringProperty(adapter + ADAPTER_PROPERTY_DESCRIPTION, ""));
				props.setWebsiteURL(PropertiesService.getStringProperty(adapter + ADAPTER_PROPERTY_WEBSITE_URL, ""));
				props.setImageURL(PropertiesService.getStringProperty(adapter + ADAPTER_PROPERTY_IMAGE_URL, ""));
				props.setSearchURL(PropertiesService.getStringProperty(adapter + ADAPTER_PROPERTY_SEARCH_URL, ""));
				props.setMethod(PropertiesService.getStringProperty(adapter + ADAPTER_PROPERTY_METHOD, "GET"));
				props.setPriority(PropertiesService.getIntegerProperty(adapter + ADAPTER_PROPERTY_PRIORITY, 1));
				props.setResultMixFactor(PropertiesService.getIntegerProperty(adapter + ADAPTER_PROPERTY_RESULTMIX_FACTOR, 1));
				props.setRelevanceMultiplier(PropertiesService.getFloatProperty(adapter + ADAPTER_PROPERTY_RELEVANCE_MULT, 1f));
				props.setRelevanceOffset(PropertiesService.getFloatProperty(adapter + ADAPTER_PROPERTY_RELEVANCE_OFFSET, 0f));
				props.setBatchSize(PropertiesService.getIntegerProperty(adapter + ADAPTER_PROPERTY_BATCH_SIZE, 100));
				props.setReRank(PropertiesService.getBooleanProperty(adapter + ADAPTER_PROPERTY_RERANK, false));
				props.setCategoryURL(PropertiesService.getStringProperty(adapter + ADAPTER_PROPERTY_CATEGORY_URL, "/"));

				// get extra properties - includes the usual properties as well
				Properties extraProperties = PropertiesService.getAllProperties(adapter);
				props.setExtraProperties(extraProperties);

				categoryURLS.put(adapter, props.getCategoryURL());
				// props.summary();
				properties.put(adapter, props);
				logger.info("Adapter " + adapter + " Ready!");
				count++;
			} catch (java.lang.ClassNotFoundException cnf) {
				logger.error("Adapter class not found specified by " + adapter + ADAPTER_PROPERTY_CLASSNAME, cnf);
			} catch (java.lang.NullPointerException npe) {
				logger.error("Adapter null instance " + ADAPTER_INSTANCES, npe);
			}

			sortSources();
		} // while
		if (count > 0) {
			logger.info("Search Adapter Factory up. " + count + " Adapters are available.");
		} else {
			logger.warn("Search Adapter Factory has no adapters to work with. Check dsm.properties.", null);
		}
	}

	/** Sorts the sources using their Display Title rather than the adapter code */
	private static void sortSources() {
		Vector values = new Vector(properties.values());

		Object[] valuesArray = new Object[values.size()];

		valuesArray = (Object[]) values.toArray(valuesArray);

		Arrays.sort(valuesArray, new AdapterTitleComparator());

		sortedSources = new String[valuesArray.length];

		for (int i = 0; i < valuesArray.length; i++) {
			SearchAdapterProperties p = (SearchAdapterProperties) valuesArray[i];
			sortedSources[i] = p.getAdapterCode();
		}
	}

	/**
	 * Returns the available source repositories as an Enumeration for web
	 * display
	 */
	public static String[] getAvailableSourceRepositories() {
		return sortedSources;
	}

	/**
	 * Returns the adapter's properties as specified by the code Can return
	 * null.
	 * 
	 * @param code
	 */
	public static SearchAdapterProperties getAdapterProperties(String code) {
		code = code.toLowerCase();
		if (properties.containsKey(code)) {
			return (SearchAdapterProperties) properties.get(code);
		} else {
			return null;
		}
	}

	/**
	 * Returns the category URI/URL of the adapter. For web use. Never returns
	 * null.
	 * 
	 * @param code
	 */
	public static String getCategoryURI(String code) {
		code = code.toLowerCase();
		return StringUtils.checkString((String) categoryURLS.get(code), "/");
	}

	/**
	 * Check if an adapter with this code exists.
	 * 
	 * @param code
	 */
	public static boolean adapterExists(String code) {
		return adapters.containsKey(code.toLowerCase());
	}

	/**
	 * Returns the adapter object for the given code. Can be null.
	 * 
	 * @param code
	 */
	public static SearchAdapter getAdapter(String code) {
		code = code.toLowerCase();
		if (adapters.containsKey(code)) {
			Class adapterClass = (Class) adapters.get(code);
			try {
				SearchAdapter adapter = (SearchAdapter) adapterClass.newInstance();
				adapter.setProperties((SearchAdapterProperties) properties.get(code));
				return adapter;
			} catch (java.lang.InstantiationException ie) {
				logger.error("PANIC: Could not instantiate adapter " + code, ie);
				return null;
			} catch (java.lang.IllegalAccessException iae) {
				logger.error("Forbidden Access to adapter " + code, iae);
				return null;
			} catch (java.lang.NullPointerException ne) {
				logger.error("PANIC: Null adapter code/properties ", ne);
				return null;
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns an array of all the sources available for searching
	 */
	public static String[] getSources() {
		if (searchableResources == null) {
			ArrayList sources = new ArrayList(ADAPTER_COUNT);
			for (Enumeration k = adapters.keys(); k.hasMoreElements();) {
				sources.add(k.nextElement());
			}
			searchableResources = new String[sources.size()];
			searchableResources = (String[]) sources.toArray(searchableResources);
		}

		return searchableResources;
	}

	/** Returns the number of available repositories - adapter instances */
	public static final int getSourceRepositoryCount() {
		return properties.size();
	}

	/** Class name display */
	public final static String classStr = "o.m.d.w.SearchAdapterFactory";
}

/**
 * Adapter Title Comparator - compares the title of each source against the
 * next. Used with java.util.Arrays sort method
 * 
 * @author gsingh
 */
class AdapterTitleComparator implements Comparator {

	/**
	 * @param o1
	 * @param o2
	 */
	public int compare(Object o1, Object o2) {
		if (null != o1 && null != o2 && o1 instanceof SearchAdapterProperties && o2 instanceof SearchAdapterProperties) {
			return ((SearchAdapterProperties) o1).getDisplayTitle().toLowerCase().compareTo(((SearchAdapterProperties) o2).getDisplayTitle().toLowerCase());
		} else {
			return -1;
		}
	} // compare

} // class AdapterTitleComparator

// -- EOF
